https://www.youtube.com/watch?v=t7MEvi9RdNI&list=PL9LUW6O9WZqgUMHwDsKQf3prtqVvjGZ6S&index=6
Template-Driven Form 內容簡單,適合新手入門
簡單,沒有複雜的驗證規則,例如:連動的欄位驗證不需要自定義validator(自定義validator很麻煩)不需要動態表單非同步行為,在取得表單實體是有限制的,需注意生命週期@Input xxx:xxxType
ngOnChanges(changes: SimpleChanges) { ... }
表單實體一開始就存在本集開始沒看文件,Kevin 老師直接寫程式
首先,先開好片頭導讀的文章,再跟著影片一起看,以後才有能力自己查文件
今天內容有:(方便search)
<select>選單<select> CompareWith input在app.module.ts要imports:[FormsModule]
但Template-Driven Form有其起手式,我們來看最簡單的形式
<form #f="ngForm"> 為了取值,透過templateRef吐出ngForm(最上層的FormGroup)
      ^^^^^^^^^^ 非必加  
    <input name="firstName" ngModel /> name跟ngModel必加
           ^^^^             ^^^^^^^ 必加,否則立刻報錯
</form>
{{ f.value | json }} 會輸出整個form的東西
請參考:NgForm (DIRECTIVE)的value屬性
NgForm、NgModel、FormControl 之間的關係,跟各自有哪些屬性、方法可用?
就我的理解
NgForm是最上層的FormGroup instance
NgModel 會從 domain model 去建立一個 FormControl
API > @angular/forms
https://angular.io/api/forms/NgForm
建一個最上層的FormGroup instance,並"綁定"form後就能:
FormGroup // 繼承 forms/AbstractControl
https://angular.io/api/forms/FormGroup
API > @angular/forms
https://angular.io/api/forms/FormControl
Tracks the value and validation status of an individual form control.
無論在Reactive Form或Template-Driven Form,都用FormControl
form裡面的input element就代表一個FormControl
<form #f="ngForm">
    <input name="firstName" ngModel /> input element這就是FormControl
</form>
FormControl包含什麼?以input element為例,包含
class FormControl extends AbstractControl {
  constructor(formState: any = null, validatorOrOpts?: ValidatorFn | AbstractControlOptions | ValidatorFn[], asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[])
  ...
  // inherited from forms/AbstractControl 繼承forms/AbstractControl
  constructor(validator: ValidatorFn, asyncValidator: AsyncValidatorFn)
  // Template-Driven Form 主要是對 屬性 的熟悉度,例如:
  // 該contorl目前的值,有4類(簡單的值、key-value pair、object、array),可先console.log出來
  value: any 
  
  // 指定一個 ValidatorFn 來驗證control的值是否合法
  validator: ValidatorFn | null
  
  // 狀態
  status: string // VALID | INVALID | PENDING(該control正在檢查) | DISABLED(此control不受檢查)
  
  // 值有沒有被改過
  dirty: boolean
  
  // method(方法),在Template-Driven Form,能用的method較少,主要是 屬性、狀態 用較多
  // 設定表單驗證
  setErrors(errors: ValidationErrors, opts: { emitEvent?: boolean; } = {}): void
<form #f="ngForm">
    <input name="firstName" ngModel #n="ngModel"/> input element這就是FormControl
</form>
{{ n.value | json }}
{{ n.valid }}
請參考FormControl有哪些 屬性 可用
https://angular.io/api/forms/FormControl
Create an initial HTML form template
https://angular.io/guide/forms#create-an-initial-html-form-template
在Template Driven Forms中,只要import FormsModule,
即使是HTML5的一般的form表單,也可適用FormsModule
import { FormsModule }   from '@angular/forms';
@NgModule({
  imports: [
    FormsModule // import FormsModule
  ],
}) 
export class AppModule { }
不用對<form>執行任何操作即可使用FormsModule
範例中的CSS跟FormsModule無關,class="form-group"單純只是bootstrap的樣式
@import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');
    <form>
      class裡的form-group與angular的FormGroup無關喔!!
      <div class="form-group"> // class不是必要的,只是bootstrap的表單樣式
        <label for="name">Name</label>
        <input type="text" class="form-control" id="name" required>
      </div>
      <button type="submit" class="btn btn-success">Submit</button>
    </form>
<select>選單遇到選單用*ngFor
<div class="form-group">
  <label for="power">Hero Power</label>
  <select class="form-control" id="power" required>
    <option *ngFor="let pow of powers" [value]="pow">{{pow}}</option>
                                       control的value可以直接帶進去
  </select>
</div>
請參考:NgSelectOption (Directive) 裡的 @Input()ngValue:any
https://angular.io/api/forms/NgSelectOption
NgSelectOption 的 屬性:
<select> CompareWith input在NgSelectOption DIRECTIVE按右邊的<>
https://angular.io/api/forms/NgSelectOption
就會進到程式碼
https://github.com/angular/angular/blob/8.2.5/packages/forms/src/directives/select_control_value_accessor.ts#L191-L256
再搜尋compareWith
const selectedCountriesControl = new FormControl();
當selectOption使用ngValue時,通常在<select>加[compareWith]指定一個function()
當傳入的值 ngValue 是 Object 的時候,若要設定預設值,要加 [compareWith]="compareFn"
<select [compareWith]="compareFn"  [formControl]="selectedCountriesControl">
    <option *ngFor="let country of countries" [ngValue]="country">
        {{country.name}}
    </option>
</select>
compareFn(c1: Country, c2: Country): boolean {
    // 用id來比對
     return c1 && c2 ? c1.id === c2.id : c1 === c2;
}
<input type="text" class="form-control" id="name"
      required
      [(ngModel)]="model.name" name="name">
      ^^^^^^^^^^^^^^^^^^^^^^^
TODO: remove this: {{model.name}}
共有3種狀態:
<form ...>
    <input _ngcontent-c68 name="firstName" ngmodel required ng-reflect-required ng-reflect-name="firstName" ng-reflect-model
    class="ng-pristine ng-invalid ng-touched">
            ^^^^^^^^^   ^^^^^^^^   ^^^^^^^^ 依input狀態,Angular自動幫你替換
</form>
範例:
.ng-valid[required], .ng-valid.required  {
  border-left: 5px solid #42A948; /* green */
}
.ng-invalid:not(form)  {
  border-left: 5px solid #a94442; /* red */
}
加在index.html 適用全專案的forms
<link rel="stylesheet" href="assets/forms.css">
<input type="text" class="form-control" id="name"
  required
  [(ngModel)]="model.name" name="name"
  #spy>
<br>TODO: remove this: {{spy.className}}
<form #f="ngForm">
      ^^ ngForm export出來的樣板變數
    <label for="name">Name</label>
    <input type="text" class="form-control" id="name"
           required minlength="3"
           ^^^^^^^  ^^^^^^^^^  直接在input加上驗證條件即可
           Template-Driven的自定義validator應該較麻煩,老師沒演示
           [(ngModel)]="model.name" name="name"
           #name="ngModel">
           ^^^^^template reference
</form>
{{ f.value | json }} 觀察form的value
<div [hidden]="name.valid || name.pristine"
               ^^^^利用template reference取到
     class="alert alert-danger">
  Name is required
</div>
用hasError('error的類型') 可知發生什麼錯誤 => 可自定義錯誤訊息
<span *ngIf="name.hasError('required')"> 當有name有required的error時
欄位必填                     ^^^^^^^^^ 此input control有無required錯誤訊息?
(可自定義錯誤訊息)
</span>
{{ name.valid }} 當control驗證失敗時  =>  false
{{ name.errors | json }} 當control的errors就會包含 => {"required":true }
<form #f="ngForm" (ngSubmit)="save(f)">
                    ^^^^^^ 方法二:直接用(ngSubmit)
                    ^^^^^^ 只能在Template Driven使用,無法用於Reactive Form
    <input name="firstName" ngModel #n="ngModel" />
    方法一:<button type="submit"        (click)="save(f)">
                   ^^^當type為submit時    ^^ 把form group的內容傳到ts裡
</form>
save(f){
    console.log(f); // 觀察
    // controls      有哪些control
    // NgForm.form   這才是FormsGroup
    //             .getRawValue 取得所有value,包含disabled的input element
    // NgForm.EventEmitter
    // NgForm.value:Object
    //        firstName: "123"
    // 
}
<form #f="ngForm">
    <input name="firstName" ngModel #n1="ngModel" />
                                    ^^ template reference不能重複
    <input name="lastName" ngModel #n2="ngModel" disabled/>
                                   ^^            ^^^^^^^^ 若disabled印不出value
</form>
{{ f.value | json }} 只會顯示 { "firstName":"" }
{{ f.form.getRawValue() | json } 用form.getRawValue()取得所有value
@Directive({ // 其實是Directive
  selector: '[appForbiddenName]',
  providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]
                        ^^^^^^必須註冊在NG_VALIDATORS         multi:true一定要加   ^^^^
})
export class ForbiddenValidatorDirective implements Validator {
  @Input('appForbiddenName') forbiddenName: string;
  
  validate(control: AbstractControl): {[key: string]: any} | null {
  ^^^^^^^^ 實作驗證用的function()       ^^^^^^^^^^^^^^^^^^^^^^^^^^回傳Object或null
    return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control) : null;
  }
}